/*
 * Copyright (C) by Argonne National Laboratory
 *     See COPYRIGHT in top-level directory
 */

/* Change for BG/L made by Hao Yu, yuh@us.ibm.com
 */

#include "mpi.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

static void handle_error(int errcode, const char *str)
{
    char msg[MPI_MAX_ERROR_STRING];
    int resultlen;
    MPI_Error_string(errcode, msg, &resultlen);
    fprintf(stderr, "%s: %s\n", str, msg);
    MPI_Abort(MPI_COMM_WORLD, 1);
}

/* this test wants to compare the hints it gets from a file with a set of
 * default hints.  These hints are specific to the MPI-IO implementation, so if
 * you want to test something besides the default you'll have to use a command
 * line argument */

typedef struct hint_defaults {
    int cb_buffer_size;
    int ind_rd_buffer_size;
    int ind_wr_buffer_size;
    const char *romio_cb_read;
    const char *romio_cb_write;
    const char *cb_config_list;
} hint_defaults;

hint_defaults UFS_DEFAULTS = {
    .cb_buffer_size = 16777216,
    .ind_rd_buffer_size = 4194304,
    .ind_wr_buffer_size = 524288,
    .romio_cb_read = "automatic",
    .romio_cb_write = "automatic",
    .cb_config_list = "*:1"
};

hint_defaults BLUEGENE_DEFAULTS = {
    .cb_buffer_size = 16777216,
    .ind_rd_buffer_size = 4194304,
    .ind_wr_buffer_size = 4194304,
    .romio_cb_read = "enable",
    .romio_cb_write = "enable",
    .cb_config_list = NULL
};

/* #undef INFO_DEBUG */

/* Test will print out information about unexpected hint keys or values that
 * differ from the default.  Since this is often interesting but rarely an
 * error, default will be to increment error count for true error conditions
 * but not print out these "interesting" non-error cases. */

static int verbose = 0;
static int test_ufs = 0;
static int test_bluegene = 0;

int main(int argc, char **argv)
{
    int i, len, nkeys, flag, mynod, default_striping_factor = 0, nprocs, errs = 0;
    MPI_File fh;
    MPI_Info info, info_used;
    char *filename, key[MPI_MAX_INFO_KEY], value[MPI_MAX_INFO_VAL];
    hint_defaults *defaults;
    int ret;

    MPI_Init(&argc, &argv);

    MPI_Comm_rank(MPI_COMM_WORLD, &mynod);
    MPI_Comm_size(MPI_COMM_WORLD, &nprocs);

/* process 0 takes the file name as a command-line argument and
   broadcasts it to other processes */
    if (!mynod) {
        i = 1;
        while ((i < argc) && strcmp("-fname", *argv)) {
            if (!strcmp("-v", *argv))
                verbose = 1;
            else if (!strcmp("-u", *argv))
                test_ufs = 1;
            else if (!strcmp("-b", *argv))
                test_bluegene = 1;
            i++;
            argv++;
        }
        if (i >= argc) {
            fprintf(stderr, "\n*#  Usage: file_info [-v] -fname filename\n\n");
            MPI_Abort(MPI_COMM_WORLD, 1);
        }
        argv++;
        len = strlen(*argv);
        filename = (char *) malloc(len + 1);
        strcpy(filename, *argv);
        MPI_Bcast(&len, 1, MPI_INT, 0, MPI_COMM_WORLD);
        MPI_Bcast(filename, len + 1, MPI_CHAR, 0, MPI_COMM_WORLD);
        MPI_Bcast(&verbose, 1, MPI_INT, 0, MPI_COMM_WORLD);
        MPI_Bcast(&test_ufs, 1, MPI_INT, 0, MPI_COMM_WORLD);
        MPI_Bcast(&test_bluegene, 1, MPI_INT, 0, MPI_COMM_WORLD);
    } else {
        MPI_Bcast(&len, 1, MPI_INT, 0, MPI_COMM_WORLD);
        filename = (char *) malloc(len + 1);
        MPI_Bcast(filename, len + 1, MPI_CHAR, 0, MPI_COMM_WORLD);
        MPI_Bcast(&verbose, 1, MPI_INT, 0, MPI_COMM_WORLD);
        MPI_Bcast(&test_ufs, 1, MPI_INT, 0, MPI_COMM_WORLD);
        MPI_Bcast(&test_bluegene, 1, MPI_INT, 0, MPI_COMM_WORLD);
    }
    if (test_ufs) {
        defaults = &UFS_DEFAULTS;
    } else if (test_bluegene) {
        defaults = &BLUEGENE_DEFAULTS;
    } else {
        defaults = NULL;
    }

/* open the file with MPI_INFO_NULL */
    ret = MPI_File_open(MPI_COMM_WORLD, filename, MPI_MODE_CREATE | MPI_MODE_RDWR,
                        MPI_INFO_NULL, &fh);
    if (ret != MPI_SUCCESS)
        handle_error(ret, "MPI_File_open");

/* check the default values set by ROMIO */
    MPI_File_get_info(fh, &info_used);
    MPI_Info_get_nkeys(info_used, &nkeys);

    if (defaults != NULL) {
        for (i = 0; i < nkeys; i++) {
            MPI_Info_get_nthkey(info_used, i, key);
            MPI_Info_get(info_used, key, MPI_MAX_INFO_VAL - 1, value, &flag);
#ifdef INFO_DEBUG
            if (!mynod)
                fprintf(stderr, "Process %d, Default:  key = %s, value = %s\n", mynod, key, value);
#endif
            if (!strcmp("striping_factor", key)) {
                default_striping_factor = atoi(value);
                /* no check */
            } else if (!strcmp("cb_buffer_size", key)) {
                if (atoi(value) != defaults->cb_buffer_size) {
                    errs++;
                    if (verbose)
                        fprintf(stderr, "cb_buffer_size is %d; should be %d\n",
                                atoi(value), defaults->cb_buffer_size);
                }
            } else if (!strcmp("romio_cb_read", key)) {
                if (strcmp(defaults->romio_cb_read, value)) {
                    errs++;
                    if (verbose)
                        fprintf(stderr, "romio_cb_read is set to %s; should be %s\n",
                                value, defaults->romio_cb_read);
                }
            } else if (!strcmp("romio_cb_write", key)) {
                if (strcmp(defaults->romio_cb_write, value)) {
                    errs++;
                    if (verbose)
                        fprintf(stderr, "romio_cb_write is set to %s; should be %s\n",
                                value, defaults->romio_cb_write);
                }
            } else if (!strcmp("cb_nodes", key)) {
                /* unreliable test -- just ignore value */
            } else if (!strcmp("romio_no_indep_rw", key)) {
                if (strcmp("false", value)) {
                    errs++;
                    if (verbose)
                        fprintf(stderr, "romio_no_indep_rw is set to %s; should be %s\n",
                                value, "false");
                }
            } else if (!strcmp("ind_rd_buffer_size", key)) {
                if (atoi(value) != defaults->ind_rd_buffer_size) {
                    errs++;
                    if (verbose)
                        fprintf(stderr, "ind_rd_buffer_size is %d; should be %d\n",
                                atoi(value), defaults->ind_rd_buffer_size);
                }
            } else if (!strcmp("ind_wr_buffer_size", key)) {
                if (atoi(value) != defaults->ind_wr_buffer_size) {
                    errs++;
                    if (verbose)
                        fprintf(stderr, "ind_wr_buffer_size is %d; should be %d\n",
                                atoi(value), defaults->ind_wr_buffer_size);
                }
            } else if (!strcmp("romio_ds_read", key)) {
                if (strcmp("automatic", value)) {
                    errs++;
                    if (verbose)
                        fprintf(stderr, "romio_ds_read is set to %s; should be %s\n",
                                value, "automatic");
                }
            } else if (!strcmp("romio_ds_write", key)) {
                /* Unreliable test -- value is file system dependent.  Ignore. */
            } else if (!strcmp("cb_config_list", key)) {
#ifndef SKIP_CB_CONFIG_LIST_TEST
                if (strcmp(defaults->cb_config_list, value)) {
                    errs++;
                    if (verbose)
                        fprintf(stderr, "cb_config_list is set to %s; should be %s\n",
                                value, defaults->cb_config_list);
                }
#endif
            }
            /* don't care about the defaults for these keys */
            else if (!strcmp("romio_cb_pfr", key)) {
            } else if (!strcmp("romio_cb_fr_types", key)) {
            } else if (!strcmp("romio_cb_fr_alignment", key)) {
            } else if (!strcmp("romio_cb_ds_threshold", key)) {
            } else if (!strcmp("romio_cb_alltoall", key)) {
            } else {
                if (verbose)
                    fprintf(stderr, "unexpected key %s (not counted as an error)\n", key);
            }
        }
    }
    MPI_Info_free(&info_used);

    MPI_File_close(&fh);

    /* delete the file */
    if (!mynod)
        MPI_File_delete(filename, MPI_INFO_NULL);
    MPI_Barrier(MPI_COMM_WORLD);

/* set new info values. */

    MPI_Info_create(&info);

/* The following four hints are accepted on all machines. They can
   be specified at file-open time or later (any number of times). */

    /* buffer size for collective I/O */
    MPI_Info_set(info, "cb_buffer_size", "8388608");

    /* number of processes that actually perform I/O in collective I/O */
    sprintf(value, "%d", nprocs / 2);
    MPI_Info_set(info, "cb_nodes", value);

    /* buffer size for data sieving in independent reads */
    MPI_Info_set(info, "ind_rd_buffer_size", "2097152");

    /* buffer size for data sieving in independent writes */
    MPI_Info_set(info, "ind_wr_buffer_size", "1048576");


/* The following three hints related to file striping are accepted only
   on some parallel file systems and are ignored elsewhere.
   They can be specified only at file-creation time; if specified later
   they will be ignored. */

    /* number of I/O devices across which the file will be striped.
     * accepted only if 0 < value < default_striping_factor;
     * ignored otherwise */
    if (default_striping_factor - 1 > 0) {
        sprintf(value, "%d", default_striping_factor - 1);
        MPI_Info_set(info, "striping_factor", value);
    } else {
        sprintf(value, "%d", default_striping_factor);
        MPI_Info_set(info, "striping_factor", value);
    }

    /* the striping unit in bytes */
    MPI_Info_set(info, "striping_unit", "131072");

#ifndef SKIP_CB_CONFIG_LIST_TEST
    /* set the cb_config_list so we'll get deterministic cb_nodes output */
    MPI_Info_set(info, "cb_config_list", "*:*");
#endif

    /* the I/O device number from which to start striping the file.
     * accepted only if 0 <= value < default_striping_factor;
     * ignored otherwise */
    sprintf(value, "%d", default_striping_factor - 2);
    MPI_Info_set(info, "start_iodevice", value);


/* The following hint about PFS server buffering is accepted only on
   Intel PFS. It can be specified anytime. */
    MPI_Info_set(info, "pfs_svr_buf", "true");

/* open the file and set new info */
    ret = MPI_File_open(MPI_COMM_WORLD, filename, MPI_MODE_CREATE | MPI_MODE_RDWR, info, &fh);
    if (ret != MPI_SUCCESS)
        handle_error(ret, "MPI_File_open");

/* check the values set */
    ret = MPI_File_get_info(fh, &info_used);
    if (ret != MPI_SUCCESS)
        handle_error(ret, "MPI_File_get_info");
    MPI_Info_get_nkeys(info_used, &nkeys);

    for (i = 0; i < nkeys; i++) {
        MPI_Info_get_nthkey(info_used, i, key);
        MPI_Info_get(info_used, key, MPI_MAX_INFO_VAL - 1, value, &flag);
#ifdef INFO_DEBUG
        if (!mynod)
            fprintf(stderr, "Process %d, key = %s, value = %s\n", mynod, key, value);
#endif
        if (!strcmp("striping_factor", key)) {
            if ((default_striping_factor - 1 > 0) && (atoi(value) != default_striping_factor - 1)) {
                errs++;
                if (verbose)
                    fprintf(stderr, "striping_factor is %d; should be %d\n",
                            atoi(value), default_striping_factor - 1);
            } else if (atoi(value) != default_striping_factor) {
                errs++;
                if (verbose)
                    fprintf(stderr, "striping_factor is %d; should be %d\n",
                            atoi(value), default_striping_factor);
            }
        } else if (!strcmp("cb_buffer_size", key)) {
            if (atoi(value) != 8388608) {
                errs++;
                if (verbose)
                    fprintf(stderr, "cb_buffer_size is %d; should be %d\n", atoi(value), 8388608);
            }
        }
        /* only check the hints we set */
        else if (!strcmp("cb_nodes", key)) {
            /* unreliable test: just skip */
        } else if (!strcmp("romio_no_indep_rw", key)) {
            if (strcmp("false", value)) {
                errs++;
                if (verbose)
                    fprintf(stderr, "romio_no_indep_rw is set to %s; should be %s\n",
                            value, "false");
            }
        } else if (!strcmp("ind_rd_buffer_size", key)) {
            if (atoi(value) != 2097152) {
                errs++;
                if (verbose)
                    fprintf(stderr, "ind_rd_buffer_size is %d; should be %d\n",
                            atoi(value), 2097152);
            }
        } else if (!strcmp("ind_wr_buffer_size", key)) {
            if (atoi(value) != 1048576) {
                errs++;
                if (verbose)
                    fprintf(stderr, "ind_wr_buffer_size is %d; should be %d\n",
                            atoi(value), 1048576);
            }
        } else if (!strcmp("romio_ds_read", key)) {
            if (strcmp("automatic", value)) {
                errs++;
                if (verbose)
                    fprintf(stderr, "romio_ds_read is set to %s; should be %s\n",
                            value, "automatic");
            }
        } else if (!strcmp("romio_ds_write", key)) {
            /* Unreliable test -- value is file system dependent.  Ignore. */
        } else if (!strcmp("cb_config_list", key)) {
#ifndef SKIP_CB_CONFIG_LIST_TEST
            if (strcmp("*:*", value)) {
                errs++;
                if (verbose)
                    fprintf(stderr, "cb_config_list is set to %s; should be %s\n", value, "*:*");
            }
#endif
        } else if (!strcmp("romio_cb_pfr", key)) {
            if (strcmp("disable", value)) {
                errs++;
                if (verbose)
                    fprintf(stderr, "romio_cb_pfr is set to %s; should be %s\n",
                            value, "automatic");
            }
        } else if (!strcmp("romio_cb_fr_types", key)) {
            if (strcmp("aar", value)) {
                errs++;
                if (verbose)
                    fprintf(stderr, "romio_cb_fr_types is set to %s; should be %s\n", value, "aar");
            }
        } else if (!strcmp("romio_cb_fr_alignment", key)) {
            if (strcmp("1", value)) {
                errs++;
                if (verbose)
                    fprintf(stderr, "romio_cb_fr_alignment is set to %s; should be %s\n",
                            value, "1");
            }
        } else if (!strcmp("romio_cb_ds_threshold", key)) {
            if (strcmp("0", value)) {
                errs++;
                if (verbose)
                    fprintf(stderr, "romio_cb_ds_threshold is set to %s; should be %s\n",
                            value, "0");
            }
        } else if (!strcmp("romio_cb_alltoall", key)) {
            if (strcmp("automatic", value)) {
                errs++;
                if (verbose)
                    fprintf(stderr, "romio_cb_alltoall is set to %s; should be %s\n",
                            value, "automatic");
            }
        }

        else {
            if (verbose)
                fprintf(stderr, "unexpected key %s (not counted as an error)\n", key);
        }
    }

    /* Q: SHOULD WE BOTHER LOOKING AT THE OTHER PROCESSES? */
    if (!mynod) {
        if (errs)
            fprintf(stderr, "Found %d errors.\n", errs);
        else
            printf(" No Errors\n");
    }

    MPI_File_close(&fh);

    /* delete the file */
    if (!mynod)
        MPI_File_delete(filename, MPI_INFO_NULL);
    MPI_Barrier(MPI_COMM_WORLD);

    free(filename);
    MPI_Info_free(&info_used);
    MPI_Info_free(&info);
    MPI_Finalize();
    return 0;
}
